/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *            Copyright (C) 2018 Institute of Computing Technology, CAS
 *               Author : Han Shukai (email : hanshukai@ict.ac.cn)
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *         The kernel's entry, where most of the initialization work is done.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this
 * software and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
 * persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * */

#include <common.h>
#include <asm.h>
#include <asm/unistd.h>
#include <os/loader.h>
#include <os/irq.h>
#include <os/sched.h>
#include <os/lock.h>
#include <os/kernel.h>
#include <os/task.h>
#include <os/string.h>
#include <os/mm.h>
#include <os/time.h>
#include <sys/syscall.h>
#include <screen.h>
#include <printk.h>
#include <assert.h>
#include <type.h>
#include <csr.h>

#define PAGESIZE 4096
extern void ret_from_exception();

typedef struct
{
    char *taskname;
    int   entry;
    int   id; // 不是pid，是exec调用的时候执行的id
    int   loc; // sd read的时候读取的地址。
    int   num;
    int   arg0;
    int   arg1;
    int   arg2;
    int   mem_address; // 读取到内存之中的地址位置。
}tasks_entry;
tasks_entry Tasks_info[20];

static void init_jmptab(void)
{
    volatile long (*(*jmptab))() = (volatile long (*(*))())KERNEL_JMPTAB_BASE;

    jmptab[CONSOLE_PUTSTR]  = (long (*)())port_write;
    jmptab[CONSOLE_PUTCHAR] = (long (*)())port_write_ch;
    jmptab[CONSOLE_GETCHAR] = (long (*)())port_read_ch;
    jmptab[SD_READ]         = (long (*)())sd_read;
    jmptab[QEMU_LOGGING]    = (long (*)())qemu_logging;
    jmptab[SET_TIMER]       = (long (*)())set_timer;
    jmptab[READ_FDT]        = (long (*)())read_fdt;
    jmptab[MOVE_CURSOR]     = (long (*)())screen_move_cursor;
    jmptab[PRINT]           = (long (*)())printk;
    jmptab[YIELD]           = (long (*)())do_scheduler;
    jmptab[MUTEX_INIT]      = (long (*)())do_mutex_lock_init;
    jmptab[MUTEX_ACQ]       = (long (*)())do_mutex_lock_acquire;
    jmptab[MUTEX_RELEASE]   = (long (*)())do_mutex_lock_release;
}
int tasknum; //需要执行的任务个数
char batch[100]; // 批处理
static void init_task_info(void)
{
    // TODO: [p1-task4] Init 'tasks' array via reading app-info sector
    // NOTE: You need to get some related arguments from bootblock first
    int i;
    int location= 0x53000000;
    int info;

    tasknum = 4;
    for (i=0; i<16; i++){
        int *ptr = location;
        info = *ptr;
        tasks[i].bytes = info;
        location += 4;
    }
    // for(i=0; i<16/2; i++){
    //     int *ptr = location;
    //     info = *ptr;
    //     tasks[2*i].loc = info & 0x000000ff;
    //     tasks[2*i].num = (info & 0x0000ff00) >> 8;
    //     tasks[2*i+1].loc = (info & 0x00ff0000) >> 16;
    //     tasks[2*i+1].num = (info & 0xff000000) >> 24;
    //     location  += 4;
    // }
    int programNameAddr = location;
    char *ptr = programNameAddr;
    for(i=0; i<TASK_MAXNUM; i++){
        tasks[i].filename = (char *)programNameAddr;
        while(*ptr != 0){
            ptr += 1;
            programNameAddr += 1;
        }
        ptr += 1;
        programNameAddr += 1;
        if(*ptr == 0xff)
        {
            bios_putstr("go to handle info\n");
            break;
        }
    }
    programNameAddr += 1;
    char *loadbatch = programNameAddr;
    int k = 0;
    while (*loadbatch)
    {
        batch[k++] = *loadbatch;
        loadbatch++;
    }

}



static void init_pcb_stack(
    ptr_t kernel_stack, ptr_t user_stack, ptr_t entry_point,
    pcb_t *pcb, int argc, int arg0, int arg1, int arg2)
{
     /* TODO: [p2-task3] initialization of registers on kernel stack
      * HINT: sp, ra, sepc, sstatus
      * NOTE: To run the task in user mode, you should set corresponding bits
      *     of sstatus(SPP, SPIE, etc.).
      */
    regs_context_t *pt_regs =
        (regs_context_t *)(kernel_stack - sizeof(regs_context_t));
        int i;
        for(i = 0; i < 32; i++){
            pt_regs->regs[i] = 0;
        }
        pt_regs->regs[13] = arg2;
        pt_regs->regs[12] = arg1;
        pt_regs->regs[11] = arg0;
        pt_regs->regs[10] = argc;
        pt_regs->regs[4] = pcb;
        pt_regs->regs[2] = user_stack;
        pt_regs->regs[1] = entry_point;
        pt_regs->sepc = entry_point;
        pt_regs->sstatus = SR_SIE;
    /* TODO: [p2-task1] set sp to simulate just returning from switch_to
     * NOTE: you should prepare a stack, and push some values to
     * simulate a callee-saved context.
     */
    switchto_context_t *pt_switchto =
        (switchto_context_t *)((ptr_t)pt_regs - sizeof(switchto_context_t));
    pcb->kernel_sp = pt_switchto;
    pt_switchto->regs[1] = pcb->kernel_sp;
    pt_switchto->regs[0] = (reg_t)&ret_from_exception; // ra寄存器的值
}

void 
init_tasks_info()
{
    int i = 0;
    for(; i<16; i++){
        int task1_offset;
        task1_offset = i + 2;
        int offset = 0x52000000 + 0x10000*i;
        int num = (tasks[task1_offset].bytes - tasks[task1_offset-1].bytes) / 512 + 1;
        int loc = (tasks[task1_offset-1].bytes/512);
        int adder = tasks[task1_offset-1].bytes % 0x200;
        int address = adder + offset;
        Tasks_info[i].entry = address;
        Tasks_info[i].id    = i;
        Tasks_info[i].loc   = loc;
        Tasks_info[i].num   = num;
        Tasks_info[i].mem_address = offset;
        Tasks_info[i].taskname = tasks[task1_offset].filename;
        Tasks_info[i].arg0  = i+1; // print location
        Tasks_info[i].arg1  = 0;
        Tasks_info[i].arg2  = 1;
    }
}

static void init_pcb(void)
{
    /* TODO: [p2-task1] load needed tasks and init their corresponding PCB */
    init_list_head(&ready_queue);
    init_tasks_info();
    // 在最开始的时候就加载所有的程序.
    int project3_tasks = 1; 
    // 这么多其实也就加载shell一个小程序.
    for(int i = 0 ; i < project3_tasks; i++){
        pcb[i].kernel_sp = allocKernelPage(1) + PAGESIZE;
        pcb[i].user_sp = allocUserPage(1) + PAGESIZE;
        pcb[i].pid = i + 1;
        pcb[i].cursor_x = 0;
        pcb[i].cursor_y = 0;
        pcb[i].status = TASK_UNUSED;
        pcb[i].locknum = 0;
        pcb[i].kernel_stack_base = pcb[i].kernel_sp;
        pcb[i].user_stack_base = pcb[i].user_sp;
        int task1_offset;
        task1_offset = i + 2;
        int offset = 0x52000000 + 0x10000*i;
        pcb[i].filename = tasks[task1_offset].filename;
        int num = (tasks[task1_offset].bytes - tasks[task1_offset-1].bytes) / 512 + 1;
        int loc = (tasks[task1_offset-1].bytes/512);
        bios_sdread(offset, num + 1, loc);
        int adder = tasks[task1_offset-1].bytes % 0x200;
        int address = adder + offset;

        init_pcb_stack(pcb[i].kernel_sp, pcb[i].user_sp, address ,&pcb[i], 0, 0, 0, 0);
        // if(i==0)
        //     enqueue(&ready_queue, &pcb[i]);
    }
    /* TODO: [p2-task1] remember to initialize 'current_running' */
    current_running = &pid0_pcb;
    pid0_pcb.list.next = &pcb[0].list;
    pcb[0].status = TASK_READY;
    enqueue(&ready_queue, &pcb[0]);
}

// Task info array
task_info_t tasks[TASK_MAXNUM];
int processnum = 1; // at the beginning, the only process is shell
extern pid_t do_exec(int id, int argc, uint64_t arg0, uint64_t arg1, uint64_t arg2)
{
    // 该函数用于执行创建一个进程。
    // 设计思路是：在最开始的时候，main函数将所有的用户进程装载到PCB之中，但是在ready queue之中只装载一个shell
    // 一旦调用exec的话，就为其分配一个PCB。并且将其装载到ready queue之中。

    // 上述的思路并不正确，因为内存执行的地址是给定的。

    // 正确的思路来啦:
    // 首先在程序之中仅仅加载shell,之后在每一次调用exec的时候来将程序加载进内存之中
    // 不用之前的tasks结构体了,太混乱了,新开一个维护一下.

    // id是我们要执行的PCB
    // 如果pcb的状态是没有使用，那么我们就将其设置为ready，并且添加到队列之中。

    pcb_t *new_pcb;
    int num;
    for(num=0; num < processnum; num++){
        if(pcb[num].status == TASK_EXITED){
            new_pcb = &pcb[num];
            new_pcb->kernel_sp = new_pcb->kernel_stack_base;
            new_pcb->user_sp = new_pcb->user_stack_base;
            new_pcb->kernel_stack_base = new_pcb->kernel_sp;
            new_pcb->user_stack_base = new_pcb->user_sp;
            break;
        }
    }
    if (num == processnum){
        new_pcb = &pcb[processnum++];
        new_pcb->kernel_sp = allocKernelPage(1) + PAGE_SIZE;
        new_pcb->user_sp = allocUserPage(1) + PAGE_SIZE;
        new_pcb->kernel_stack_base = new_pcb->kernel_sp;
        new_pcb->user_stack_base = new_pcb->user_sp;
    }
    int offset, readnum, loc;
    offset = Tasks_info[id].mem_address;
    readnum = Tasks_info[id].num;
    loc = Tasks_info[id].loc;
    bios_sdread(offset, readnum + 1, loc);
    int address = Tasks_info[id].entry;
    init_pcb_stack(new_pcb->kernel_sp, new_pcb->user_sp, address, new_pcb, 3, Tasks_info[id].arg0, Tasks_info[id].arg1, Tasks_info[id].arg2);
    new_pcb->pid = process_id++;
    new_pcb->status = TASK_READY;
    new_pcb->cursor_x = 0;
    new_pcb->cursor_y = 0;
    new_pcb->locknum = 0;
    new_pcb->filename = pcb[id].filename;
    add_queue(&ready_queue, new_pcb);
    init_list_head(&new_pcb->wait_list);
    do_scheduler();
    return new_pcb->pid;
}

static void syscall_error(void)
{
    printk("Error syscall\n");
    while (1)
        ;
}

static void init_syscall(void)
{
    // TODO: [p2-task3] initialize system call table.
    int i = 0;
    for(i = 0; i < NUM_SYSCALLS; i++)
    {
        syscall[i] = &handle_other;
    }
    syscall[SYSCALL_SLEEP] = (long (*)())do_sleep;
    syscall[SYSCALL_YIELD] = (long (*)())do_scheduler;
    syscall[SYSCALL_WRITE] = (long (*)())screen_write;
    syscall[SYSCALL_CURSOR] = (long (*)())screen_move_cursor;
    syscall[SYSCALL_REFLUSH] = (long (*)())screen_reflush;
    syscall[SYSCALL_GET_TIMEBASE] = (long (*)())get_time_base;
    syscall[SYSCALL_GET_TICK] = (long (*)())get_ticks;
    syscall[SYSCALL_LOCK_INIT] = (long (*)())do_mutex_lock_init;
    syscall[SYSCALL_LOCK_ACQ] = (long (*)())do_mutex_lock_acquire;
    syscall[SYSCALL_LOCK_RELEASE] = (long (*)())do_mutex_lock_release;

    // add syscall console getchar
    syscall[SYSCALL_READCH] = (long (*)())bios_getchar;
    syscall[SYSCALL_KILL]   = (long (*)())do_kill;
    syscall[SYSCALL_PS] = (long (*)())do_ps;
    syscall[SYSCALL_EXEC] = (long (*)())do_exec;
    syscall[SYSCALL_EXIT] = (long (*)())do_exit;
}

int main(void)
{
    // Init jump table provided by kernel and bios(ΦωΦ)
    init_jmptab();

    // Init task information (〃'▽'〃)
    init_task_info();

    // Init Process Control Blocks |•'-'•) ✧
    init_pcb();
    printk("> [INIT] PCB initialization succeeded.\n");

    // Read CPU frequency (｡•ᴗ-)_
    time_base = bios_read_fdt(TIMEBASE);

    // Init lock mechanism o(´^｀)o
    init_locks();
    printk("> [INIT] Lock mechanism initialization succeeded.\n");

    // Init interrupt (^_^)
    init_exception();
    printk("> [INIT] Interrupt processing initialization succeeded.\n");

    // Init system call table (0_0)
    init_syscall();
    printk("> [INIT] System call initialized successfully.\n");

    // Init screen (QAQ)
    init_screen();
    printk("> [INIT] SCREEN initialization succeeded.\n");

    // TODO: [p2-task4] Setup timer interrupt and enable all interrupt globally
    // NOTE: The function of sstatus.sie is different from sie's
    bios_set_timer(get_ticks() + TIMER_INTERVAL);   
    uint64_t pid0_addr = (uint64_t)&pid0_pcb;

    asm volatile(
        "nop\n"
        "mv tp, %0\n"
        :
        :"r"(pid0_addr)
    );
    // Infinite while loop, where CPU stays in a low-power state (QAQQQQQQQQQQQ)
    while (1)
    {
        // If you do non-preemptive scheduling, it's used to surrender control
        // do_scheduler();
        // If you do preemptive scheduling, they're used to enable CSR_SIE and wfi
        
        // it should be time interrupt but for debug, disable the time interrupt
        do_scheduler();
        // enable_preempt();
        // bios_set_timer(get_timer() + TIMER_INTERVAL);
        // asm volatile("wfi");
    }

    return 0;
}
